﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;

namespace Reborn_WorldServer.App1AppHandler
{
    public class SocketController
    {
        private Socket socket = null;

        private object lockObj = new Object();
        private List<byte[]> queuedSends = new List<byte[]>();

        private int allowNumParallelSends = 5;
        private int pendingSends = 0;

        private long currentSendID = 0;

        private ClientSocketData csd;

        public SocketController(Socket socket, ClientSocketData csd)
        {
            sendResult = new AsyncCallback(processResult);

            this.socket = socket;
            this.csd = csd;
        }

        private bool closingSocket = false;
        public void Close()
        {
            lock (lockObj)
            {
                if (closingSocket == true)
                    return;

                closingSocket = true;

                closeSocket();
            }
        }

        private void closeSocket()
        {
            try
            {
                socket.Shutdown(SocketShutdown.Both);
                socket.Close(5);
                socket.Dispose();
            }
            catch { }
        }

        public bool Connected
        {
            get { return socket.Connected; }
        }

        public IAsyncResult BeginReceive(byte[] dataBuffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state)
        {
            bool failure = false;
            lock (lockObj)
            {
                //Do not attempt begin recieve if the socket is closing.
                if (closingSocket)
                    return null;
            }

            //BeginRecieve may run the callback syncronously syncronously so dont call it from within a lock.
            try
            {
                return socket.BeginReceive(dataBuffer, offset, size, socketFlags, callback, state);
            }
            catch
            {
                queuedSends.Clear();
                failure = true;
            }

            if (failure == true)
            {
                //Invoke this on a different thread so we're not inside multiple locks
                Action<ClientSocketData> closeSocketFunction = Form1.instance.closeClientSocket;
                closeSocketFunction.BeginInvoke(csd, null, null);
            }

            return null;
        }
        public int EndReceive(IAsyncResult asyncResult)
        {
            bool failure = false;
            try
            {
                return socket.EndReceive(asyncResult);
            }
            catch
            {
                queuedSends.Clear();
                failure = true;
            }

            if (failure == true)
                Form1.instance.closeClientSocket(csd);

            return 0;
        }

        public int Receive(byte[] buffer)
        {
            bool failure = false;
            try
            {
                return socket.Receive(buffer);
            }
            catch
            {
                queuedSends.Clear();
                failure = true;
            }

            if (failure == true)
                Form1.instance.closeClientSocket(csd);

            return 0;
        }

        public int Available
        {
            get { return socket.Available; }
        }

        public void BeginSend(string message)
        {
            BeginSend(message, false, false);
        }
        public void BeginSend(string message, bool nonCritical, bool closeAfterSend)
        {
            bool failure = false;
            lock (lockObj)
            {
                if (closingSocket)
                    return;

                currentSendID++;

                string orderedMessage = "<data id=\"" + currentSendID.ToString() + "\">" + message + "</data>";
                byte[] byData = System.Text.Encoding.ASCII.GetBytes(orderedMessage + "\0");

                if (pendingSends >= allowNumParallelSends)
                {
                    if (queuedSends.Count > 1000)
                    {
                        queuedSends.Clear();
                        failure = true;
                    }
                    else
                        queuedSends.Add(byData);
                }
                else
                {
                    try
                    {
                        if (closeAfterSend)
                        {
                            socket.BeginSend(byData, 0, byData.Length, SocketFlags.None, sendResult, true);
                            closingSocket = true;
                        }
                        else
                        {
                            socket.BeginSend(byData, 0, byData.Length, SocketFlags.None, sendResult, false);
                        }

                        pendingSends++;
                    }
                    catch
                    {
                        queuedSends.Clear();
                        failure = true;
                    }
                }
            }

            if (failure == true)
            {
                //Invoke this on a different thread so we're not inside multiple locks
                Action<ClientSocketData> closeSocketFunction = Form1.instance.closeClientSocket;
                closeSocketFunction.BeginInvoke(csd, null, null);
            }
        }

        private AsyncCallback sendResult;
        private void processResult(IAsyncResult result)
        {
            SocketError se;

            try
            {
                socket.EndSend(result, out se);
            }
            catch (ObjectDisposedException ode)
            {
                queuedSends.Clear();

                //If the socket has been closed by us, dont kick them.
                return;
            }
            catch (Exception ex)
            {
                queuedSends.Clear();
                Form1.instance.closeClientSocket(csd);

                return;
            }


            bool closeSocket = (bool)result.AsyncState;
            if (closeSocket)
            {
                //Call this to update the main form and clean up the dead socket
                Form1.instance.closeClientSocket(csd);

                //Previous call will not be able to close the socket due to (closingSocket = true) so force close it here.
                this.closeSocket();

                return;
            }


            bool failure = false;
            lock (lockObj)
            {
                pendingSends--;

                if (queuedSends.Count > 0 && closingSocket == false)
                {
                    byte[] queuedSend = queuedSends[0];
                    queuedSends.RemoveAt(0);

                    try
                    {
                        socket.BeginSend(queuedSend, 0, queuedSend.Length, SocketFlags.None, sendResult, false);
                        pendingSends++;
                    }
                    catch
                    {
                        queuedSends.Clear();
                        failure = true;
                    }
                }
            }

            if (failure == true)
                Form1.instance.closeClientSocket(csd);
        }
    }
}
